/*
 ============================================================================
 Name        : octal.c
 Author      : 
 Version     :
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <assert.h>
#include <sys/types.h>
#include <string.h>
#include <getopt.h>

#define OCTAL_BYTE_SIZE 3
#define DEFAULT_INPUT_OUTPUT "-"
#define DEFAULT_DELIMITER ""
#define DEFAULT_OCTAL_BYTES 0
#define DELIMITER_ERROR "Error al parsear el delimitador"
#define MODE_ERROR "Error: Argumentio invalido -b -O"
#define ZERO_STRING "0"
#define BYTE_SIZE 1

static int octal_flag = 1;

static struct option long_options[] = {
		{ "octal", no_argument, &octal_flag, 1 },
		{ "binary", no_argument, &octal_flag, 0 },
		{ "input", required_argument, 0, 'i' },
		{ "output", required_argument, 0, 'o' },
		{ "length", required_argument, 0, 'l' },
		{ "delimiter", required_argument, 0, 'd' },
		{ "help",no_argument, 0, 'h' },
		{ "version", no_argument, 0, 'v' },
		{ 0, 0, 0, 0 } };

char HELP[] = "Usage:\n"
		"	tp0 -h\n"
		"	tp0 -V\n"
		"	tp0 [options]\n"
		"Options:\n"
		"	-h,	--help Print this information.\n"
		"	-V,	--version Print version and quit.\n"
		"	-O,	--octal Octal dump mode: encode the input (binary file) in octal\n"
		"		format (default).\n"
		"	-b,	--binary Binary mode: decode the input (octal dump file) in binary\n"
		"		format.\n"
		"	-i,	--input Path to input file (defaults to stdin).\n"
		"	-o,	--output Path to output file (defaults to stdout).\n"
		"	-l, --length Number of octal bytes per line (line length) at the\n"
		"		output in octal dump mode (0 = default = no limit).\n"
		"	-d, --delimiter Delimiter between octal bytes. Could be a string, not only\n"
		"		a single byte. Used as delimiter for octal output and input\n"
		"		files (default: none).\n"
		"Examples:\n"
		"	tp0\n"
		"	tp0 -i -\n"
		"	tp0 -i - -o - -O\n"
		"	tp0 -b\n"
		"	tp0 -i /dev/random -o /tmp/random-octaldump -l 12\n";

void usage() {
	printf("%s", HELP);
}

void version() {
	printf("tp0 version 0.1\n");
}

void char2octal(char c, unsigned char* oct) {
	oct[0] = (c & 0xC0) >> 6;
	oct[1] = (c & 0x38) >> 3;
	oct[2] = (c & 0x07);
}

void octal2char(unsigned char* oct, unsigned char* c) {
	int i;
	char a = 0;
	char to_int[2];
	to_int[1] = (char) NULL;
	for (i = 0; i < OCTAL_BYTE_SIZE; i++) {
		to_int[0] = oct[i];
		a |= ((atoi(to_int)) << (3 * (2 - i)));
	}
	*c = (unsigned char) a;
}

int to_octal(FILE* in, FILE* out, int length, char* del) {
	char data;
	int i;
	int bytes = DEFAULT_OCTAL_BYTES;
	unsigned char octal[OCTAL_BYTE_SIZE];


	if(fread(&data, BYTE_SIZE, 1, in)!=1){
		perror("Error al leer el archivo de entrada: ");
		return EXIT_FAILURE;
	}

	while (!feof(in)) {
		memset((void*) octal, 0, OCTAL_BYTE_SIZE);

		char2octal(data, octal);
		for (i = 0; i < OCTAL_BYTE_SIZE; i++) {
			fprintf(out, "%d", (int) *(octal + i));
		}
		bytes++;
		fread(&data, BYTE_SIZE, 1, in);

		if ((length > 0) && (bytes == length)) {
			fprintf(out, "\n");
			bytes = 0;
		} else {
			if (!feof(in))
				fprintf(out, "%s", del);
		}

	}
	return EXIT_SUCCESS;
}

int to_bin(FILE* in, FILE* out, int length, char* del) {
	unsigned char byte;
	int octal_bytes = 0;
	unsigned char octal[OCTAL_BYTE_SIZE];
	int delimeter_len = strlen(del);
	char* del_read = malloc(delimeter_len);
	/*lo inicialico vacio al string*/
	del_read[0] = (char) NULL;

	if(fread((void*) octal, OCTAL_BYTE_SIZE, 1, in)!=1){
		perror("Error al leer el archivo de entrada: ");
		return EXIT_FAILURE;
	}

	while (!feof(in)) {
		octal2char(octal, &byte);
		/*lo bajo al stream*/
		if(fputc(byte, out)!=byte){
			perror("Error al escribir al archivo de salida: ");
			return EXIT_FAILURE;
		}
		octal_bytes++;
		if (length == octal_bytes) {
			octal_bytes = 0;
			/*leo el fin de linea*/
			fread((void*) octal, BYTE_SIZE, BYTE_SIZE, in);
			fread((void*) octal, OCTAL_BYTE_SIZE, 1, in);
		} else {
			/*leo delimitador, si es 1 delimiter_len, no lee nada*/
			fread((void*) del_read, delimeter_len, BYTE_SIZE, in);
			fread((void*) octal, OCTAL_BYTE_SIZE, BYTE_SIZE, in);
			if ((strcmp(del_read, del) != 0) && (!feof(in))) { //comparo q sea el mismo
				fprintf(stderr, "%s (%x != %x)\n", DELIMITER_ERROR, *del_read, *del);
				free(del_read);
				return EXIT_FAILURE;
			}
		}
	}
	return EXIT_SUCCESS;
}

int main(int argc, char **argv) {
	int c;
	int res = EXIT_SUCCESS;
	FILE* in = stdin;
	FILE* out = stdout;
	char* del = malloc(1);
	int length = 0;
	int option_index = 0;
	del[0] = (char)NULL;

	while ((c = getopt_long(argc, argv, "i:o:l:d:hvbO", long_options, &option_index)) != -1) {
		switch (c) {
		case 'O':
			octal_flag = 1;
			break;
		case 'b':
			octal_flag = 0;
			break;
		case 'i':
			if (strcmp(optarg, DEFAULT_INPUT_OUTPUT) != 0) {
				in = fopen(optarg, "r");
				if (in == NULL) {
					fprintf(stderr, "Error al abrir el archivo de entrada.\n");
					return EXIT_FAILURE;
				}
			}
			break;
		case 'o':
			if (strcmp(optarg, DEFAULT_INPUT_OUTPUT) != 0) {
				out = fopen(optarg, "w");
				if (out == NULL) {
					fprintf(stderr, "Error al abrir el archivo de salida.\n");
					return EXIT_FAILURE;
				}
			}
			break;
		case 'l':
			length = atoi(optarg);
			if ((length == 0) && (strcmp(optarg, ZERO_STRING) != 0)) {
				fprintf(stderr, "Error al parsear la cantidad de bytes octales.\n");
				return EXIT_FAILURE;
			}
			break;
		case 'd':
			free(del);
			del = optarg;
			break;

		case 'v':
			version();
			break;
		default:
			break;
		}
	}

	if (octal_flag)
		res = to_octal(in, out, length, del);
	else
		res = to_bin(in, out, length, del);

	//free(del);
	fclose(in);
	fclose(out);

	return res;
}
